﻿// -----------------------------------------------------------------------
// <copyright file="DictionaryEntry.cs" company="G.W. van der Vegt">
// SharpForth is inspired by JonesForthInC v1.48 and bb4wForth.
// </copyright>
// -----------------------------------------------------------------------

namespace SharpForth
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Diagnostics;

    /// <summary>
    /// Flags are currently unused.
    /// 
    /// Name is case-sensitive.
    /// 
    /// Definitions are either 65536*the DictionaryEntry Index or a 32 Bits Literal Value.
    /// 
    /// This allows registers like esi to have:
    /// 
    /// The Upper Word = 1* the DictionaryEntry Index and 
    /// The Lower Word = 4* the Index of the DictionaryEntry Definition.
    /// 
    /// The Lower word is multiplied by 4 to mimic Word Addressing.
    /// 
    /// +------------------------+------------------+--------------+
    /// | DICTIONARY INDEX       | DEFINITION INDEX | TARGET FLAGS |
    /// |                        |                  |              |
    /// +--- (16 bits) ----------+- (14 bits) ------+- (2 bits) ---+
    /// 
    /// Either Definitions or a SharpCode Delegate can be used.
    /// 
    /// Execute() takes care of LIT processing.
    /// </summary>
    public class DictionaryEntry
    {
        #region Constructors

        /// <summary>
        /// Constructor.
        /// </summary>
        public DictionaryEntry()
        {
            this.Name = String.Empty;
            this.Flags = 0;
            this.Definition = new List<Int32>();
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="Name">The Word Name</param>
        /// <param name="Flags">The Forth Word Flags</param>
        public DictionaryEntry(String Name, Int32 Flags)
            : this()
        {
            this.Name = Name;
            this.Flags = Flags;
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="Name">The Word Name</param>
        /// <param name="Flags">The Forth Word Flags</param>
        /// <param name="Definition">The Forth Words implementing this word</param>
        public DictionaryEntry(String Name, Int32 Flags, Int32[] Definition)
            : this(Name, Flags)
        {
            foreach (Int32 D in Definition)
            {
                this.Definition.Add(D);
            }
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="Name">The Word Name</param>
        /// <param name="Flags">The Forth Word Flags</param>
        /// <param name="SharpCode">The delegate implementing this word</param>
        public DictionaryEntry(String Name, Int32 Flags, DictionaryEntryDelegate SharpCode)
            : this(Name, Flags)
        {
            this.SharpCode += SharpCode;
        }

        /// <summary>
        /// Copy Constructor.
        /// </summary>
        /// <param name="previousEntry">The DictionaryEntry to copy</param>
        public DictionaryEntry(DictionaryEntry previousEntry)
        {
            this.Name = previousEntry.Name;
            this.Definition = previousEntry.Definition;
            this.Flags = previousEntry.Flags;
            this.SharpCode = previousEntry.SharpCode;
        }

        #endregion Constructors

        /// <summary>
        /// The delagate used for implementing Forth Words in C#.
        /// </summary>
        /// <param name="self">The DictionaryEntry itself</param>
        public delegate void DictionaryEntryDelegate(DictionaryEntry self);

        #region Events

        /// <summary>
        /// C# Code implemented as Delegate.
        /// </summary>
        public event DictionaryEntryDelegate SharpCode;

        #endregion Events

        #region Properties

        ///// <summary>
        ///// Current wordt being Executed.
        ///// </summary>
        //public Int32 CurrentWord
        //{
        //    get;
        //    private set;
        //}

        /// <summary>
        /// Forth Code.
        /// </summary>
        public List<Int32> Definition
        {
            get;
            private set;
        }

        /// <summary>
        /// Forth Word Flags.
        /// </summary>
        public Int32 Flags
        {
            get;
            private set;
        }

        /// <summary>
        /// Returns true if this word contains Forth Code (ie Definition.Count>0).
        /// </summary>
        public Boolean IsForthCode
        {
            get
            {
                return Definition.Count != 0;
            }
        }

        /// <summary>
        /// Returns true if this word contains C# Code.
        /// </summary>
        public Boolean IsSharpCode
        {
            get
            {
                return SharpCode != null;
            }
        }

        /// <summary>
        /// The Name of this Forth Word.
        /// </summary>
        public String Name
        {
            get;
            private set;
        }

        #endregion Properties

        #region Methods

        //public void Skip(Int32 skip = 1)
        //{
        //    CurrentWord += skip;
        //}

        /// <summary>
        /// Invoke either C# Delegate or Interpret Forth Definition.
        /// </summary>
        public void Invoke()
        {
            String trace = String.Empty;

            //! This makes sure tracing starts at a words edge.

            Boolean isTracing = Forth.TRACE;

            if (isTracing)
            {
                Debug.Indent();
            }

            String before = isTracing ? String.Format("( {0}", String.Join(", ", Forth.PARAM_STACK.ToArray().Reverse())) : String.Empty;

            if (IsSharpCode)
            {
                if (isTracing)
                {
                    trace = String.Format("{0}.Invoke() ", Name.PadLeft(16));
                    trace = trace.PadRight(80 - ((Debug.IndentLevel) * Debug.IndentSize) - trace.Length);

                    trace += before;
                    trace += Forth.PARAM_STACK.Count == 0 ? " )".Trim() : " )";

                    Debug.Print(trace);
                }
                SharpCode(this);
            }
            else if (IsForthCode || Definition.Count == 0)
            {
                if (isTracing)
                {
                    trace = String.Format("{0}.Execute()", Name.PadLeft(16));
                    trace = trace.PadRight(80 - ((Debug.IndentLevel) * Debug.IndentSize) - trace.Length);

                    trace += before;
                    trace += Forth.PARAM_STACK.Count == 0 ? " )".Trim() : " )";

                    Debug.Print(trace);
                }
                Execute();
            }

            if (isTracing)
            {                        //{0}.Execute()
                trace = String.Format("{0}        ->", Name.PadLeft(16));
                trace = trace.PadRight(80 - (Debug.IndentLevel * Debug.IndentSize) - trace.Length);
                trace += before;

                //! Note we have some 'pollution' here from WORD (the address/length).
                String after = Forth.PARAM_STACK.Count == 0 ? String.Empty : " " + String.Join(", ", Forth.PARAM_STACK.ToArray().Reverse());
                Debug.Print(String.Format("{0} --{1} )", trace, after));
                Debug.Unindent();
            }
        }

        /// <summary>
        /// Toggle Hide Flag.
        /// </summary>
        public void ToggleHide()
        {
            Flags = Flags ^ Forth.FORTH_HIDDEN_MASK;
        }

        /// <summary>
        /// Debug Friendly Output.
        /// </summary>
        /// <returns>The Forth Word</returns>
        public override String ToString()
        {
            if (IsSharpCode)
            {
                return String.Format("{0} {1} {2} {3}", Forth.Dictionary.IndexOf(this), Name, Flags, "C#");
            }
            else if (IsForthCode)
            {
                return String.Format("{0} {1} {2} {3}", Forth.Dictionary.IndexOf(this), Name, Flags, String.Join(", ", Definition.ToArray()));
            }

            return String.Format("{0} {1} {2}", Forth.Dictionary.IndexOf(this), Name, Flags);
        }

        internal void ToggleImmed()
        {
            Flags = Flags ^ Forth.FORTH_IMMED_MASK;
        }

        /// <summary>
        /// Simply recursivly execute Mixed C# and 
        /// Pure Forth Words without using the RSP Stack yet!
        /// 
        /// LIT is taken care of inside Execute().
        /// DOCOL Should be taken care of inside Execute().
        /// </summary>
        private void Execute()
        {
            if (SharpCode != null)
            {
                SharpCode(this);
            }
            else
            {
                Forth.PUSH_R_STACK(Forth.I2A(Forth.Dictionary.IndexOf(this)));

                Int32 CurrentWord = 0;

                //! This is a good location for debugging! 
                //! Just edit it with the correct Forth Word 
                //! and enable the Debugger.Break Statement.
                //! Like Words.ZBRANCH.Value.Name or a String.

                //if (Name.Equals(".S"))
                //{
                //    Debugger.Break();
                //}

                if (Definition.Count == 0 || Definition[CurrentWord] != Forth.Locate(Words.DOCOL.Value.Name))
                {
                    if (Definition.Count != 0)
                    {
                        //Debug.Print(Forth.WarningMarker + String.Format("The Forth Word {0} does not start with DOCOL", this.Name));
                    }
                    else
                    {
                        //Debug.Print(Forth.WarningMarker + String.Format("The Forth Word {0} is Empty", this.Name));
                    }

                    Forth.PUSH_P_STACK(Forth.Locate(this.Name));
                }
                else
                {

                    while (CurrentWord < Definition.Count)
                    {
                        Int32 ndx = Definition[CurrentWord] >> 16;

                        //! TODO Set DP correctlty here!
                        if (Forth.Dictionary[ndx].Name.Equals(Words.LIT.Value.Name))
                        {
                            //! -------------------------
                            //! Take Special Care of LIT!
                            //! -------------------------

                            CurrentWord++;

                            Forth.PARAM_STACK.Push(Definition[CurrentWord++]);
                        }
                        else if (Forth.Dictionary[ndx].Name.Equals(Words.DOCOL.Value.Name))
                        {
                            //! -------------------------
                            //! Ignore DOCOL
                            //! -------------------------

                            CurrentWord++;
                        }
                        else if (Forth.Dictionary[ndx].Name.Equals(Words.LITSTRING.Value.Name))
                        {
                            //! 2507

                            // Get String Length
                            CurrentWord++;
                            Int32 len = Definition[CurrentWord];

                            // push the address of the start of the string
                            Forth.PARAM_STACK.Push((Forth.Dictionary.IndexOf(this)) << 16 | CurrentWord + 1 << 2);
                            Forth.PARAM_STACK.Push(len);
                            unchecked
                            {
                                //! Round Bytecount to Int32count
                                len = (Int32)((len + 3) & (3 ^ 0xFFFFFFFF));
                            }
                            CurrentWord += len >> 2;

                            CurrentWord++;
                        }
                        else if (Forth.Dictionary[ndx].Name.Equals(Words.PAREN_ABORT_QUOTE.Value.Name))
                        {
                            // Get String Length
                            CurrentWord++;
                            Int32 len = Definition[CurrentWord];

                            String msg = String.Empty;

                            Int32ByteUnion dat = new Int32ByteUnion();

                            for (Int32 i = 0; i < len; i++)
                            {
                                if (i % 4 == 0)
                                {
                                    CurrentWord++;
                                }
                                dat.iVal = Definition[CurrentWord];
                                msg = msg + (Char)dat[i % 4];
                            }

                            Int32 code = Forth.POP_P_STACK();

                            if (code != 0)
                            {
                                throw new AbortException(code, msg);
                            }

                            CurrentWord++;
                        }
                        else if (Forth.Dictionary[ndx].Name.Equals(Words.BRANCH.Value.Name))
                        {
                            //! -------------------------
                            //! Implementation of BRANCH.
                            //! -------------------------

                            CurrentWord++;
                            Int32 ofs = Definition[CurrentWord];

                            CurrentWord += ofs >> 2;
                        }
                        else if (Forth.Dictionary[ndx].Name.Equals(Words.ZBRANCH.Value.Name))
                        {
                            //! -------------------------
                            //! Implementation of 0BRANCH.
                            //! -------------------------

                            Int32 con = Forth.POP_P_STACK();

                            CurrentWord++;
                            Int32 ofs = Definition[CurrentWord];

                            if (con == Forth.FORTH_FALSE)
                            {
                                //! Skip to after THEN
                                CurrentWord += (ofs >> 2);
                            }
                            else
                            {
                                //! Skip Offset.
                                CurrentWord++;
                            }
                        }
                        else if (Forth.Dictionary[ndx].Name.Equals(Words.EXIT.Value.Name))
                        {
                            //Just break the Loop the nice way.
                            //CurrentWord = Definition.Count;

                            break;
                        }
                        else if (Forth.Dictionary[ndx].Name.Equals(Words.DOES.Value.Name))
                        {
                            //Skip DOES>
                            CurrentWord++;

                            //Start Copying up to EXIT.
                            while (CurrentWord < Definition.Count - 1)
                            {
                                Forth.Dictionary.Last().Definition.Add(Definition[CurrentWord++]);
                            }

                            break;
                        }
                        else if (Forth.Dictionary[ndx].Name.Equals(Words.SEE.Value.Name))
                        {
                            //! Only when executing compiled words
                            switch (Forth.STATE)
                            {
                                //! Immediate mode (executing a compiled word in immediate mode).
                                case Forth.COMPILE_MODE:
                                    Forth.DoOutput(Forth.ErrorMarker + "SEE invoked in COMPILE_MODE.", true);
                                    break;

                                //! Normal mode (executing a compiled word).
                                //! Effectivly we're skipping SEE and silently replace it by (SEE).
                                case Forth.IMMEDIATE_MODE:
                                    CurrentWord++;

                                    Forth.PARAM_STACK.Push(Definition[CurrentWord]);
                                    CurrentWord++;

                                    Words.PAREN_SEE.Value.Invoke();

                                    break;
                            }
                        }
                        else if (Forth.Dictionary[ndx].Name.Equals(Words.PAREN_LOOP.Value.Name))
                        {
                            //! Get Loop Parameters.

                            //! POP2RSP()
                            Int32 index = Forth.POP_R_STACK();
                            Int32 limit = Forth.POP_R_STACK();

                            index++;
                            if (index >= limit)
                            {
                                //! Discard Offset.
                                CurrentWord++;

                                //! Continue after Loop.
                                CurrentWord++;
                            }
                            else
                            {
                                //! Jump.
                                CurrentWord += Definition[CurrentWord + 1] >> 2;

                                //! Skip (DO).
                                CurrentWord++;

                                //! Save Loop Parameters.
                                //! PUSH2RSP()
                                Forth.PUSH_R_STACK(limit);  //limit
                                Forth.PUSH_R_STACK(index);  //index
                            }
                        }
                        else if (Forth.Dictionary[ndx].Name.Equals(Words.PAREN_PLUS_LOOP.Value.Name))
                        {
                            //! Get Loop Parameters.

                            //! POP2RSP()
                            Int32 index = Forth.POP_R_STACK(); // eax : index
                            Int32 limit = Forth.POP_R_STACK(); // edx : limit

                            Int32 n = Forth.POP_P_STACK();     // ebx : step

                            Int32 tmp1 = index - limit;
                            Int32 tmp2 = tmp1 + n;                  // add step

                            if ((tmp1 ^ tmp2) < 0)     // bb4wForth : step
                            {
                                //! Discard Offset.
                                CurrentWord++;

                                //! Continue after Loop.
                                CurrentWord++;
                            }
                            else
                            {
                                //! Jump - (index-limit) and (index-limit+n) have differents signs).
                                CurrentWord += Definition[CurrentWord + 1] >> 2;

                                //! Skip (DO).
                                CurrentWord++;

                                index = index + n;

                                //! Save Loop Parameters.
                                //! PUSH2RSP()
                                Forth.PUSH_R_STACK(limit);  //limit
                                Forth.PUSH_R_STACK(index);  //index
                            }
                        }
                        else
                        {
                            Forth.Dictionary[ndx].Invoke();

                            CurrentWord++;
                        }
                    }
                }

                if (Forth.RETURN_STACK.Count == 0)
                {
                    Debug.Print(" Empty Return Stack");
                }
                Forth.POP_R_STACK();
            }
        }

        #endregion Methods
    }

}
